﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support;

namespace Noris.Schedule.Planning.ProcessData
{
    #region AuditorCls : logování
    public class AuditorCls
    {
        public AuditorCls()
        {
            this._AuditItems = new List<PlanningResultInfoCls>();
            this._AuditUnique = new Dictionary<PlanningResultInfoCls, object>();
        }
        /// <summary>
        /// Připraví auditování (vyčistí prostor pro záznamy).
        /// V průběhu procesu se volá metoda AuditAdd().
        /// Na konci procesu lze vypsat obsah auditlogu do trace metodou AuditSaveToTrace(), nebo lze získat seznam položek pro zobrazení (this.AuditItems).
        /// </summary>
        internal void AuditorClear()
        {
            this._AuditItems = new List<PlanningResultInfoCls>();
            this._AuditUnique = new Dictionary<PlanningResultInfoCls, object>();
        }
        /// <summary>
        /// Opíše aktuální položky Auditlogu do trace procesu (odkud je může přebírat Noris)
        /// </summary>
        internal void AuditSaveToTrace()
        {
            Steward.AuditInfo("Výsledky plánovacího procesu, databázový profil: " + Steward.Connect.ProfileName);

            List<PlanningResultInfoCls> auditItems = new List<PlanningResultInfoCls>(this._AuditItems);     // Nový List, nikoli kopie reference na tentýž
            auditItems.Sort(PlanningResultInfoCls.CompareForAudit);
            foreach (PlanningResultInfoCls item in auditItems)
                item.SaveToAudit();
        }
        /// <summary> { get; }
        /// Souhrn všech výstupních informací o plánovacím procesu
        /// </summary>
        internal List<PlanningResultInfoCls> AuditItems { get { return _AuditItems; } }
        private List<PlanningResultInfoCls> _AuditItems;
        private Dictionary<PlanningResultInfoCls, object> _AuditUnique;
        /// <summary>
        /// Do interního registru informací přidá další záznam, hlášení o problému nebo stav.
        /// Toto přetížení obsahuje položkovou identifikaci záznamu Noris.
        /// </summary>
        /// <param name="severity">Závažnost informace</param>
        /// <param name="circumstance">Určení místa, kde k události došlo</param>
        /// <param name="document">Specifikace dokumentu Green, který informaci vyvolal. Položkový dokument.</param>
        /// <param name="message">Text zprávy</param>
        internal void AddInfo(PlanningResultSeverityType severity, PlanningResultCircumstanceType circumstance, IRecordItemKey document, string message)
        {
            this._ResultInfoListAdd(new PlanningResultInfoCls(this._AuditItems.Count + 1, severity, circumstance, document, message));
        }
        /// <summary>
        /// Do interního registru informací přidá další záznam, hlášení o problému nebo stav.
        /// Toto přetížení obsahuje hlavičkovou identifikaci záznamu Noris.
        /// </summary>
        /// <param name="severity">Závažnost informace</param>
        /// <param name="circumstance">Určení místa, kde k události došlo</param>
        /// <param name="document">Specifikace dokumentu Green, který informaci vyvolal. Hlavička dokumentu nebo nepoložkový záznam.</param>
        /// <param name="message">Text zprávy</param>
        internal void AddInfo(PlanningResultSeverityType severity, PlanningResultCircumstanceType circumstance, IRecordKey document, string message)
        {
            this._ResultInfoListAdd(new PlanningResultInfoCls(this._AuditItems.Count + 1, severity, circumstance, document, message));
        }
        /// <summary>
        /// Do interního registru informací přidá další záznam, hlášení o problému nebo stav
        /// </summary>
        /// <param name="severity">Závažnost informace</param>
        /// <param name="circumstance">Určení místa, kde k události došlo</param>
        /// <param name="axisID">ID záznamu materiálové osy</param>
        /// <param name="taskID">ID záznamu úkolu</param>
        /// <param name="message">Text zprávy</param>
        internal void AddInfo(PlanningResultSeverityType severity, PlanningResultCircumstanceType circumstance, int axisID, int taskID, string message)
        {
            this._ResultInfoListAdd(new PlanningResultInfoCls(this._AuditItems.Count + 1, severity, circumstance, axisID, taskID, message));
        }
        /// <summary>
        /// Přidá informaci do seznamu (pro zobrazování) a loguje ji (do trace)
        /// </summary>
        /// <param name="planningResultInfoCls"></param>
        private void _ResultInfoListAdd(PlanningResultInfoCls resultInfo)
        {
            if (resultInfo == null) return;

            bool add = true;
            if (resultInfo.IsUnique)
            {   // Pokud vstupující záznam má být uložen jen jedenkrát:
                add = !(this._AuditUnique.ContainsKey(resultInfo));          // add = true, když záznam ještě nemáme.
                if (add)
                    this._AuditUnique.Add(resultInfo, null);
            }
            if (add)
                this._AuditItems.Add(resultInfo);
        }
    }
    #endregion
    #region CLASS PlanningResultInfoCls : jedna výstupní informace (řádek) o stavu / problému / chybě v průběhu plánovacího procesu
    /// <summary>
    /// PlanningResultInfoCls : jedna výstupní informace (řádek) o stavu / problému / chybě v průběhu plánovacího procesu
    /// </summary>
    public class PlanningResultInfoCls
    {
        #region KONSTRUKCE
        internal PlanningResultInfoCls(int infoId, PlanningResultSeverityType severity, PlanningResultCircumstanceType circumstance, IRecordKey document, string message)
        {
            _InfoID = infoId;
            _Time = DateTime.Now;
            _Severity = severity;
            _Circumstance = circumstance;
            if (document != null)
            {
                _ClassNumber = document.ClassNumber;
                _FolderNumber = 0;
                _RecordNumber = document.RecordNumber;
                _ObjectNumber = 0;
            }
            _Message = message;
            _CalcHash();
        }
        internal PlanningResultInfoCls(int infoId, PlanningResultSeverityType severity, PlanningResultCircumstanceType circumstance, IRecordItemKey document, string message)
        {
            _InfoID = infoId;
            _Time = DateTime.Now;
            _Severity = severity;
            _Circumstance = circumstance;
            if (document != null)
            {
                _ClassNumber = document.ClassNumber;
                _FolderNumber = document.FolderNumber;
                _RecordNumber = document.CisloSubjektu;
                _ObjectNumber = document.CisloObjektu;
            }
            _Message = message;
            _CalcHash();
        }
        internal PlanningResultInfoCls(int infoId, PlanningResultSeverityType severity, PlanningResultCircumstanceType circumstance, int axisID, int taskID, string message)
        {
            _InfoID = infoId;
            _Time = DateTime.Now;
            _Severity = severity;
            _Circumstance = circumstance;
            _AxisID = axisID;
            _TaskID = taskID;
            _Message = message;
            _CalcHash();
        }
        /// <summary>
        /// ID informace
        /// </summary>
        public int InfoID { get { return _InfoID; } }
        private int _InfoID;
        /// <summary>
        /// Přesný okamžik události
        /// </summary>
        public DateTime Time { get { return _Time; } }
        private DateTime _Time;
        /// <summary>{ get; }
        /// Závažnost informace
        /// </summary>
        public PlanningResultSeverityType Severity { get { return _Severity; } }
        private PlanningResultSeverityType _Severity;
        /// <summary>{ get; }
        /// Okolnosti, za kterých došlo k události (v jaké části algoritmu se to stalo, při jaké činnosti)
        /// </summary>
        public PlanningResultCircumstanceType Circumstance { get { return _Circumstance; } }
        private PlanningResultCircumstanceType _Circumstance;
        /// <summary>{ get; }
        /// Číslo třídy, povinně
        /// </summary>
        public int ClassNumber { get { return _ClassNumber; } }
        private int _ClassNumber;
        /// <summary>{ get; }
        /// Číslo pořadače, nebo 0 když není známo (nebude se předávat do auditlogu)
        /// </summary>
        public int FolderNumber { get { return _FolderNumber; } }
        private int _FolderNumber;
        /// <summary>{ get; }
        /// Číslo subjektu/nonsubjektu
        /// </summary>
        public int RecordNumber { get { return _RecordNumber; } }
        private int _RecordNumber;
        /// <summary>{ get; }
        /// Číslo objektu, nebo 0 když nejsou položky
        /// </summary>
        public int ObjectNumber { get { return _ObjectNumber; } }
        private int _ObjectNumber;
        /// <summary>{ get; }
        /// Číslo záznamu materiálové osy, nebo 0 když není určen
        /// </summary>
        public int AxisID { get { return _AxisID; } }
        private int _AxisID;
        /// <summary>{ get; }
        /// Číslo záznamu kapacitního úkolu, nebo 0 když není určen
        /// </summary>
        public int TaskID { get { return _TaskID; } }
        private int _TaskID;
        /// <summary>
        /// Text informace
        /// </summary>
        public string Message { get { return _Message; } }
        private string _Message;
        #endregion
        #region GetHashCode, Equals, Unique
        public override int GetHashCode()
        {
            return this._Hash;
        }
        public override bool Equals(object obj)
        {
            PlanningResultInfoCls other = obj as PlanningResultInfoCls;
            if (other == null) return false;

            return (this.ClassNumber == other.ClassNumber &&
                    this.RecordNumber == other.RecordNumber &&
                    this.ObjectNumber == other.ObjectNumber &&
                    this.Circumstance == other.Circumstance);
        }
        /// <summary>
        /// Určí hodnoty _IsUnique a _Hash
        /// </summary>
        private void _CalcHash()
        {
            _IsUnique = (this._ClassNumber != 0 && this._RecordNumber != 0 && this._Circumstance != PlanningResultCircumstanceType.Other);
            if (_IsUnique)
                _Hash = this._ClassNumber.GetHashCode() ^
                        this._RecordNumber.GetHashCode() ^
                        this._ObjectNumber.GetHashCode() ^
                        this._Circumstance.GetHashCode();
        }
        /// <summary>
        /// Obsahuje příznak true, pokud tento záznam má být ukládán jen jedenkrát (jeho neustálé opakování není zajímavé).
        /// To je tehdy, když záznam obsahuje identifikaci záznamu Noris a smysluplnou hodnotu Circumstance.
        /// </summary>
        public bool IsUnique { get { return _IsUnique; } }
        private bool _IsUnique;
        private int _Hash;
        #endregion
        #region Podpora (zápis do trace, třídění)
        /// <summary>
        /// Zapíše svoje informace do Auditlogu plánovací tabule.
        /// Auditlog se po skončení PT opisuje do Noris auditlogu (ale to řeší NrsCowley, který PT / PP spustil).
        /// </summary>
        /// <returns></returns>
        internal void SaveToAudit()
        {
            string message = this.Circumstance.ToString() + ": " + this.Message;
            Steward.AuditWithContext(this._AuditType, message, null, this.ClassNumber, this.RecordNumber, this.FolderNumber);
        }
        /// <summary>
        /// Obsahuje typ AuditRowType pro zdejší typ PlanningResultSeverityType this.Severity
        /// </summary>
        private AuditRowType _AuditType
        {
            get
            {
                switch (this.Severity)
                {
                    case PlanningResultSeverityType.Info: return AuditRowType.Info;
                    case PlanningResultSeverityType.Warning: return AuditRowType.Warning;
                    case PlanningResultSeverityType.UserError: return AuditRowType.ApplicationError;
                    case PlanningResultSeverityType.SystemError: return AuditRowType.CoreError;
                }
                return AuditRowType.Info;
            }
        }
        /// <summary>
        /// Obsahuje číslo odpovídající typu AuditRowType, určené pro třídění do auditlogu (nejprve SysError, pak UserError, pak Warning, pak Info a nakonec ostatní.
        /// </summary>
        private int _AuditTypeSort
        {
            get
            {
                switch (this.Severity)
                {
                    case PlanningResultSeverityType.Info: return 4;
                    case PlanningResultSeverityType.Warning: return 3;
                    case PlanningResultSeverityType.UserError: return 2;
                    case PlanningResultSeverityType.SystemError: return 1;
                }
                return 99;
            }
        }
        /// <summary>
        /// Třídí záznamy PlanningResultInfoCls v pořadí Severity (E-W-I), ClassNumber DESC (tj. třída 0 bude nakonec), Circumstance (typ problému), zpráva (text)
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareForAudit(PlanningResultInfoCls a, PlanningResultInfoCls b)
        {
            if (a == null || b == null) return 0;

            int cmp = a._AuditTypeSort.CompareTo(b._AuditTypeSort);
            if (cmp != 0) return cmp;

            cmp = b.ClassNumber.CompareTo(a.ClassNumber);
            if (cmp != 0) return cmp;

            cmp = ((int)a.Circumstance).CompareTo((int)b.Circumstance);
            if (cmp != 0) return cmp;

            cmp = a.Message.CompareTo(b.Message);
            if (cmp != 0) return cmp;

            cmp = a.AxisID.CompareTo(b.AxisID);
            if (cmp != 0) return cmp;

            return 0;
        }
        #endregion
    }
    #endregion
}
